12章 イテレータとジェネレータ
https://gyazo.com/51ba590422cb6160a4960060509a9941
ES2015ではイテレータ(iterator)とジェネレータ(generator)という2つの重要な概念が追加された。
12.1 イテレータ
イテレータ(反復子)
繰り返しのための機構
関連
反復可能なオブジェクト(iterable object)
イテレータオブジェクト(iterator object)
イテレータといった時通常はこっちを指す
↑この2つは相反するものではない。
反復可能かつイテレータでもあるオブジェクトもある
12.1.1 配列とイテレータ
12.1.1.1 配列は反復可能なオブジェクト
配列はiterable object
String, map, setも
反復可能なオブジェクトについてはfor...ofを使って各要素について処理できる
12.1.1.2 配列はイテレータではない
配列は反復可能なオブジェクト(iterable object)ではあるが、イテレータ(iterator object)ではない
イテレータ
メソッドnextを呼び出すことで次々と要素を取り出せるオブジェクトのことを指す
ES2015ではvaluesメソッドを使って配列を簡単にイテレータに変換することができる
code:js
const it = array.values();
ブラウザの実装に注意
nextを呼び出すとvalueとdoneという2つのプロパティを持つオブジェクトが返される
value: 値
doneがtrueのときvalueはundefinedになる
done : すべての要素を出したか(真偽値)
doneがtrueになってからnextを何度呼び出しても同じものが返ってくる
同じ配列から複数のイテレータを生成することができる。
12.1.2 イテレータのプロトコル
イテレータであるための条件
イテレータプロトコルを実装する必要がある
メソッドnextを呼ぶとvalueとdoneを持つオブジェクトを返すようなメソッドを実装
code:js
class Log {
constructor() {
this.messages = [];
}
add(message) {
const now = Date.now(); /* 現在時刻を表す整数。15章参照 */
console.log(ログ追加: ${message}(${now}));
this.messages.push({ message, timestamp: now });
}
return this.messages.values();
}
}
12.1.3 無限の値を供給するイテレータ
code: js
class FibonacciSequence {
let a = 0, b = 1;
return {
next() {
let rval = { value: b, done: false };
b += a;
a = rval.value;
return rval;
}
};
}
}
無限の値を供給するイテレータはfor...ofを使うと無限ループになるのでbreakするようにする
12.2 ジェネレータ
ジェネレータ(generator)
関数の一種
普通の関数との違い
関数は制御と値を任意の場所から呼び出し側に戻すことができる
ジェネレータを呼び出すときにはすぐに実行されず、まずはイテレータが戻される。そのあとで、イテレータのメソッドnextを呼び出すたびに実行が進む
function*: ジェネレータの定義
呼び出し側に値を供給するためにはキーワードyieldが使われる
returnも使われるが通常は値を返すためには使われない
code:js
function* rainbow() { /* 「*」でジェネレータであることを示す */
yield '赤';
yield '橙';
yield '黄';
yield '緑';
yield '青';
yield '水色';
yield '紫';
}
const it = rainbow(); /* イテレータを取得 */
console.log(it.next()); // { value: '赤', done: false }
console.log(it.next()); // { value: '橙', done: false }
console.log(it.next()); // { value: '黄', done: false }
console.log(it.next()); // { value: '緑', done: false }
console.log(it.next()); // { value: '青', done: false }
console.log(it.next()); // { value: '水色', done: false }
console.log(it.next()); // { value: '紫', done: false }
console.log(it.next()); // { value: undefined, done: true }
console.log(it.next()); // { value: undefined, done: true }
console.log("--------");
for(let color of rainbow()) {
console.log(color);
}
/* 実行結果
赤
橙
黄
緑
青
水色
紫
*/
12.2.1 yield式と双方向コミュニケーション
ジェネレータを使うと呼び出し側との間で双方向のコミュニケーションが可能になる
yield
yieldは式なので評価の結果何らかの値になることになる
next呼び出し時の引数の値になる
ジェネレータは呼び出し側から呼び出される関数の実行を制御するという強力な機能を持っている
code:js
function* interrogate() { /* 質問する */
const name = yield "お名前は?";
const color = yield "お好きな色は何ですか?";
return ${name}さんの好きな色は${color}だそうですよ。;
}
const it = interrogate(); /* イテレータが返る */
console.log(it.next()); /* 最初の1回は値を渡さない(渡しても無視される)*/
// { value: 'お名前は?', done: false }
console.log(it.next('楓'));
// { value: 'お好きな色は何ですか?', done: false }
console.log(it.next('緑'));
// { value: '楓さんの好きな色は緑だそうですよ。', done: true }
console.log(it.next());
// { value: undefined, done: true }
12.2.2 ジェネレータとreturn
ジェネレータのどこかでreturnを呼び出すとdoneがtrueになりvalueプロパティはreturnに指定した値になる。
code:js
function* abc() {
yield 'a';
yield 'b';
return 'c';
}
const it = abc();
console.log(it.next()); // { value: 'a', done: false }
console.log(it.next()); // { value: 'b', done: false }
console.log(it.next()); // { value: 'c', done: true } // これは正しい動作だが、ジェネレータを利用する側はdoneになったときにvalueを無視してしまうこともあるので注意(e.g. for...of)
for(let l of abc()) { //
console.log(l);
}
/* 実行結果 (cは出力されない)
a
b
*/
12.3 まとめ
セットやオブジェクトが複数の値を供給するためのものである場合にイテレータとするとfor...ofなどの標準的な手法を使って処理を書ける
「必要になるまで計算をしないでおく」という処理を可能にする